(function () {
    // 游戏配置
    var config = {
        easy: {
            HERO_MISSILE_COUNT_MAX: 50,
            ENEMY_COUNT_MAX: 10,
            ENEMY_MISSILE_COUNT_MAX: 1,
            ENERY_CAN_COUNT_MAX: 3,
            COLLIDED_BY_ENEMY_COST: 10,
            CATCH_ENERGY_CAN_GAIN: 10,
            CATCH_AMMO_BOX_GAIN: 10,
            HIT_ENEMY_GAIN: 3,
            MISS_ENEMY_LOST: 1,
            INIT_SCORE: 50,
            SPEED_BASE: 1
        },
        medium: {
            HERO_MISSILE_COUNT_MAX: 30,
            ENEMY_COUNT_MAX: 20,
            ENEMY_MISSILE_COUNT_MAX: 3,
            ENERY_CAN_COUNT_MAX: 2,
            COLLIDED_BY_ENEMY_COST: 20,
            CATCH_ENERGY_CAN_GAIN: 10,
            CATCH_AMMO_BOX_GAIN: 8,
            HIT_ENEMY_GAIN: 2,
            MISS_ENEMY_LOST: 1,
            INIT_SCORE: 30,
            SPEED_BASE: 2
        },
        hard: {
            HERO_MISSILE_COUNT_MAX: 20,
            ENEMY_COUNT_MAX: 20,
            ENEMY_MISSILE_COUNT_MAX: 5,
            ENERY_CAN_COUNT_MAX: 2,
            COLLIDED_BY_ENEMY_COST: 30,
            CATCH_ENERGY_CAN_GAIN: 5,
            CATCH_AMMO_BOX_GAIN: 5,
            HIT_ENEMY_GAIN: 1,
            MISS_ENEMY_LOST: 1,
            INIT_SCORE: 20,
            SPEED_BASE: 3
        },
        GAME_DATA_KEY: 'space_fighting_data'

    }

    /** 战场范围 */
    var Field = {
        width: 1200,
        height: 800
    }

    // 定义全局变量
    var autoid = 1;
    var explodes_at = [];

    // 定义全局函数

    /** 绘制爆炸效果 */
    function drawExplosion() {
        for (let i = 0; i < explodes_at.length; i++) {
            document.getElementById('explosions').innerHTML +=
                '<div id="exp' + autoid + '" class="explode" style="left:' + explodes_at[i].left + 'px;top:' + explodes_at[i].top + 'px"></div>';
            explodes_at.splice(i, 1);
            var id = "exp" + autoid;
            setTimeout(function () {
                document.getElementById(id).remove();
            }, 600);
            autoid++;
            Game.sounds.destroyed.currentTime = 0;
            Game.sounds.destroyed.play();
        }
        explodes_at = [];

    }



    // 事件定义

    var on_score_change = new CustomEvent('score_change', {
        detail: {}
    });
    var on_life_value_change = new CustomEvent('life_value_change', {
        detail: {}
    });
    var on_missile_count_change = new CustomEvent('missile_count_change', {
        detail: {}
    });

    // 事件监听

    document.addEventListener('score_change', function (e) {
        Game.hero.tune_score(e.detail.value);
    })

    document.addEventListener('life_value_change', function (e) {
        Game.hero.tune_life_value(e.detail.value);
    })
    document.addEventListener('missile_count_change', function (e) {
        Game.hero.tune_missile_count(e.detail.value);
    })

    // 键盘按键按下
    document.addEventListener('keydown', function (e) {
        if (e.key === "p" || e.key === "P") {
            if (e.target.id !== "player-name") {
                Game.paused = !Game.paused;
                Game.start();
            }
        }
        if (Game.is_over || Game.paused) {
            return;
        }
        if (e.keyCode === 37) {
            // Left
            Game.hero.moving = true;
            Game.hero.dir = "left";
        }
        if (e.keyCode === 38) {
            // Up
            Game.hero.moving = true;
            Game.hero.dir = "up";
        }
        if (e.keyCode === 39) {
            // Right
            Game.hero.moving = true;
            Game.hero.dir = "right";
        }
        if (e.keyCode === 40) {
            // Left
            Game.hero.moving = true;
            Game.hero.dir = "down";
        }
        if (e.keyCode === 32) {
            // Spacebar (fire)
            Game.hero.fire();
        }

    });

    // 键盘按键释放
    document.addEventListener('keyup', function (e) {
        if (e.keyCode >= 37 && e.keyCode <= 40) {
            Game.hero.moving = false;
            Game.hero.dir = "";
        }
        if (e.keyCode === 32) {
            Game.hero.shooting = false;
        }
    });

    // 游戏开始画面 开始游戏按钮 点击
    document.getElementById("start-game-button").addEventListener('click', function (e) {
        document.getElementById("start-screen").style.display = "none";
        document.getElementById("battle-field").style.display = "block";

        Game.start();
    });

    // 游戏开始画面 玩家昵称输入框 按键释放
    document.getElementById("player-name").addEventListener('keyup', function (e) {
        if (e.target.value != "") {
            if (Game.ready) {
                document.getElementById("start-game-button").disabled = false;
                Game.current_user = e.target.value;
                if (e.key == "Enter") {
                    document.getElementById("start-game-button").click();
                }
            }
        } else {
            document.getElementById("start-game-button").disabled = true;
        }

    });
    // 游戏结束画面 重新开始按钮 点击
    document.getElementById("restart-game-button").addEventListener('click', function () {
        Game.init();
    });

    // 游戏开始画面 选择游戏难度 难度改变
    document.getElementById("difficulty").addEventListener('change', function (e) {
        Game.cfg = config[e.target.value];
        Game.difficulty = e.target.options[e.target.selectedIndex].text;
        Game.init();
    });

    // 游戏开始画面 清除游戏数据按钮 点击
    document.getElementById("clear-data").addEventListener('click', function (e) {
        localStorage.removeItem(config.GAME_DATA_KEY);
        e.target.disabled = true;
    });



    // 相关对象与构造函数定义



    /** 帮助对象 */
    var H = {
        get_rand_drop_pos(obj_width, far_top = -3000) {
            return [Math.floor(Math.random() * (Field.width - obj_width)),
                Math.floor(Math.random() * far_top)
            ]
        },
        collided(obj1, obj2) {
            return (obj1.left < obj2.left + obj2.width) &&
                (obj1.left + obj1.width > obj2.left) &&
                (obj1.top < obj2.top + obj2.height) &&
                (obj1.top + obj1.height > obj2.top);
        },
        q(id) {
            return document.getElementById(id);
        },
        qc(_class) {
            return document.getElementsByClassName(_class);
        }

    }

    /** 游戏角色集合类 */
    var RoleCollection = function () {};
    RoleCollection.prototype = Object.create(Array.prototype);
    RoleCollection.prototype.constructor = RoleCollection;

    RoleCollection.prototype.append = RoleCollection.prototype.push;
    RoleCollection.prototype.move = function () {
        for (let role of this) {
            role.move();
        }
    }
    RoleCollection.prototype.destroyDoneRoles = function () {
        for (var i = 0; i < this.length; i++) {
            if (this[i].to_be_destroyed) { // 如果需要被摧毁
                if (this[i].el) { //删除 HTML 元素，如果有的话
                    this[i].el.remove();
                }
                this.splice(i, 1);
            };
        }
    }

    function Role(width, height, left, top, speed, append_to, class_name, id_name) {
        this.width = width;
        this.height = height;
        this.speed = speed;
        this.el = null;
        this.append_to = append_to;
        this.class_name = class_name;
        this.id_name = id_name;
        this.to_be_destroyed = false;
        this.left = left;
        this.top = top;
        this.left = left;
        this.top = top;
    }
    Role.prototype.draw = function () {
        if (!this.el) {
            let el = null;
            if (this.id_name) {
                el = document.getElementById(this.id_name);
                if (!el) {
                    el = document.createElement("div");
                    el.id = this.id_name;
                    if (this.class_name) {
                        el.className = this.class_name;
                    }

                }

            } else if (this.class_name) {
                el = document.createElement("div");
                el.className = this.class_name;
                if (this.id_name) {
                    el.id = this.id_name;
                }
            }
            if (this.append_to) {
                let p = H.q(this.append_to);
                if (!p) {
                    let msg = `ID 为 ${this.append_to} 的页面元素丢失！`;
                    alert(msg);
                    throw msg;
                }
                document.getElementById(this.append_to).append(el);
            }
            this.el = el;

        }
        // 定位到页面
        this.el.style.left = this.left + "px";
        this.el.style.top = this.top + "px";
    }

    Role.prototype.init_pos = function (left, top) {
        this.left = left;
        this.top = top;
        this.draw();
    }

    /** 玩家战机类 */
    function Hero(width, height, left, top, speed) {
        Role.call(this, width, height, left, top, speed, "battle-field", "hero", "hero");
        this.moving = false;
        this.dir = "";
        this.shooting = false;
        this.life_value = 100;
        this.missile_count = Game.cfg.HERO_MISSILE_COUNT_MAX;
        this.score = Game.cfg.INIT_SCORE;
        this.init_pos(left, top);
    }
    Hero.prototype = Object.create(Role.prototype);
    Hero.prototype.constructor = Hero;

    Hero.prototype.move = function () {
        // 上下左右移动
        if (this.moving) {
            if (this.dir == "left" && !(this.left <= 0)) {
                this.left -= this.speed;
            } else if (this.dir == "right" && !(this.left >= Field.width - this.width)) {
                this.left += this.speed;
            } else if (this.dir == "up" && !(this.top <= 0)) {
                this.top -= this.speed;
            } else if (this.dir == "down" && !(this.top + this.height >= Field.height)) {
                this.top += this.speed;
            }

            this.draw();
        }
        // 与敌机相撞？
        for (let enemy of Game.enemies) {
            if (this.collided_by(enemy)) {
                this.el.style.animation = null;
                this.el.offsetHeight;
                this.el.style.animation = 'hero-blink 0.2s 2';


                on_score_change.detail.value = Game.cfg.HIT_ENEMY_GAIN
                document.dispatchEvent(on_score_change);
                // Or: this.tune_score(+Game.cfg.HIT_ENEMY_GAIN);

                on_life_value_change.detail.value = -Game.cfg.COLLIDED_BY_ENEMY_COST
                document.dispatchEvent(on_life_value_change);
                // Or: this.tune_life_value(-Game.cfg.COLLIDED_BY_ENEMY_COST);

                explodes_at.push({
                    left: enemy.left,
                    top: enemy.top
                })
                enemy.init_pos(...H.get_rand_drop_pos(enemy.width));

            }
        }
        // 接住能量筒？
        for (let can of Game.energy_cans) {
            if (this.get_energy_can(can)) {
                this.tune_life_value(Game.cfg.CATCH_ENERGY_CAN_GAIN);
                can.init_pos(...H.get_rand_drop_pos(can.width));
                Game.sounds.charging.currentTime = 0;
                Game.sounds.charging.play();
            }
        }
        for (let ammo of Game.ammo_boxes) {
            if (this.get_ammo_box(ammo)) {
                this.tune_missile_count(Game.cfg.CATCH_AMMO_BOX_GAIN);
                ammo.init_pos(...H.get_rand_drop_pos(ammo.width));
                Game.sounds.reloading.currentTime = 0;
                Game.sounds.reloading.play();
            }
        }

    }
    /** 开火 */
    Hero.prototype.fire = function () {
        if (!this.shooting && this.missile_count > 0) {
            Game.hero_missiles.append(new HeroMissile(10, 28, this.left + 20, this.top - 20));
            this.missile_count--;
            this.tune_missile_count(-1);
            Game.sounds.shoot.currentTime = 0;
            Game.sounds.shoot.play();
            this.shooting = true;
        }
    }
    Hero.prototype.collided_by = function (enemy) {
        return H.collided(this, enemy);
    }
    Hero.prototype.get_energy_can = function (energy_can) {
        return H.collided(this, energy_can);
    }
    Hero.prototype.get_ammo_box = function (ammo) {
        return H.collided(this, ammo);

    }
    Hero.prototype.tune_life_value = function (val) {
        this.life_value += val;
        if (this.life_value > 100) {
            this.life_value = 100;
        }
        if (this.life_value <= 0) {
            this.life_value = 0;
            Game.over();
        }
        document.getElementById("life-indicator").value = this.life_value;
        document.getElementById("life-indicator-text").innerText = this.life_value + "%";
    }
    Hero.prototype.tune_missile_count = function (count) {
        this.missile_count += count;
        if (this.missile_count > Game.cfg.HERO_MISSILE_COUNT_MAX) {
            this.missile_count = Game.cfg.HERO_MISSILE_COUNT_MAX;
        }
        if(this.missile_count<0){
            this.missile_count=0;
        }
        document.getElementById("missile-count").innerText = this.missile_count;
    }
    Hero.prototype.tune_score = function (value) {
        this.score += value;
        document.getElementById("score").innerText = this.score < 0 ? 0 : this.score;
        if (this.score <= 0) {
            this.score = 0;
            Game.over();
        }
    }


    /** 敌军战机类 */
    function Enemy(width, height, left, top, speed) {
        Role.call(this, width, height, left, top, speed, "enemies", "enemy", null);
        if (left === null && left === null) {
            var pos = H.get_rand_drop_pos(width);
            left = pos[0];
            top = pos[1];
        }
        this.init_pos(left, top);
    }
    Enemy.prototype = Object.create(Role.prototype);
    Enemy.prototype.constructor = Enemy;
    Enemy.prototype.move = function () {
        if (this.top < Field.height) {
            this.top += this.speed;
            // 被导弹击中？
            for (let missile of Game.hero_missiles) {
                if (this.hitted_by(missile)) {
                    this.init_pos(...(H.get_rand_drop_pos(this.width)));
                    missile.to_be_destroyed = true;
                    on_score_change.detail.value = Game.cfg.HIT_ENEMY_GAIN;
                    document.dispatchEvent(on_score_change);
                    // 或者：Game.hero.tune_score(Game.cfg.HIT_ENEMY_GAIN);
                    explodes_at.push({
                        left: missile.left - 35,
                        top: missile.top - 65
                    });
                }
            }
            this.draw();
        } else {
            Game.hero.tune_score(-Game.cfg.MISS_ENEMY_LOST);
            this.init_pos(...H.get_rand_drop_pos(this.width));
        }
    }
    Enemy.prototype.hitted_by = function (missile) {
        return H.collided(this, missile);
    }



    /** 能量筒类 */
    function EnergyCan(width, height, left, top, speed) {
        Role.call(this, width, height, left, top, speed, "energy-cans", "energy-can", null);
        if (left === null && left === null) {
            var pos = H.get_rand_drop_pos(width);
        }
        this.init_pos(pos[0], pos[1]);
    }
    EnergyCan.prototype = Object.create(Role.prototype);
    EnergyCan.prototype.constructor = EnergyCan;
    EnergyCan.prototype.move = function () {
        if (this.top > Field.height) {
            var pos = H.get_rand_drop_pos(this.width);
            this.init_pos(pos[0], pos[1]);
        } else {
            this.top += this.speed;
        }
        this.draw();
    }



    /** 弹药箱类 */
    function AmmoBox(width, height, left, top, speed) {
        Role.call(this, width, height, left, top, speed, "ammo-boxes", "ammo-box", null);
        if (left === null && left === null) {
            var pos = H.get_rand_drop_pos(width);
            left = pos[0];
            top = pos[1];
        }
        this.init_pos(left, top);
    }
    AmmoBox.prototype = Object.create(Role.prototype);
    AmmoBox.prototype.constructor = AmmoBox;
    AmmoBox.prototype.move = function () {
        if (this.top > Field.height) {
            var pos = H.get_rand_drop_pos(this.width);
            this.init_pos(pos[0], pos[1]);
        } else {
            this.top += this.speed;
        }
        this.draw();
    }




    /** 玩家战机导弹类 */
    function HeroMissile(width, height, left, top, speed) {
        Role.call(this, width, height, left, top, 8, "hero-missiles", "hero-missile", null);
        this.done = false;
    }
    HeroMissile.prototype = Object.create(Role.prototype);
    HeroMissile.prototype.constructor = HeroMissile;

    HeroMissile.prototype.move = function () {
        if (this.top >= 0 - this.height) {
            this.top -= this.speed;
            this.draw();
        } else {
            this.to_be_destroyed = true;
        }
    }




    /** 游戏类 */
    var Game = {
        cfg: config.easy,
        difficulty: '容易',
        sounds: {},
        sounds_loaded: 0,
        intv: null,
        ready: false,
        current_user: '',
        hero: null,
        enemies: null,
        energy_cans: null,
        ammo_boxes: null,
        hero_missiles: null,
        paused: false,
        is_over: false,
        init() {
            Game.paused = false;
            Game.is_over = false;
            Game.hero = new Hero(50, 50, 575, 745, 5);
            Game.init_info();
            H.q("start-screen").style.display = "flex";
            H.q("battle-field").style.display = "none";
            H.q("game-over").style.display = "none";
            var els = H.qc("need-erase");
            for (var i = 0; i < els.length; i++) {
                els[i].innerHTML = "";
            }
            H.q("clear-data").disabled = localStorage.getItem(config.GAME_DATA_KEY) === null;

            Game.hero_missiles = new RoleCollection();

            Game.enemies = new RoleCollection();
            for (var i = 0; i < Game.cfg.ENEMY_COUNT_MAX; i++) {
                Game.enemies.append(new Enemy(50, 60, null, null, 3));
            }
            Game.energy_cans = new RoleCollection();

            for (var i = 0; i < Game.cfg.ENERY_CAN_COUNT_MAX; i++) {
                Game.energy_cans.append(new EnergyCan(20, 32, null, null, 4));
            }
            Game.ammo_boxes = new RoleCollection();

            for (var i = 0; i < Game.cfg.ENERY_CAN_COUNT_MAX; i++) {
                Game.ammo_boxes.append(new AmmoBox(40, 21, null, null, 4));
            }


            var sound_names = ['background', 'shoot', 'destroyed', 'charging', 'reloading'];
            for (var i = 0; i < sound_names.length; i++) {
                var sound = new Audio();
                if (sound_names[i] == "background") {
                    sound.loop = true;
                }
                Game.sounds[sound_names[i]] = sound;
                sound.addEventListener('canplaythrough', function (e) {
                    Game.sounds_loaded++;
                    if (Game.sounds_loaded == sound_names.length) {
                        Game.ready = true;
                    }
                });
                sound.src = "sound/" + sound_names[i] + ".mp3";
            }
            var instruction =
                '<li>1. 每击中一架敌机 <img src="images/enemy.png" style="width: 30px;height: auto"> 得 ' + Game.cfg.HIT_ENEMY_GAIN + ' 分；</li>' +
                '<li>2. 每逃走一架敌机 <img src="images/enemy.png" style="width: 30px;height: auto"> 失 ' + Game.cfg.MISS_ENEMY_LOST + ' 分；</li>' +
                '<li>3. 与敌机 <img src="images/enemy.png" style="width: 30px;height: auto"> 相撞得 ' + Game.cfg.HIT_ENEMY_GAIN + ' 分，失去 ' + Game.cfg.COLLIDED_BY_ENEMY_COST + '% 生命值；</li>' +
                '<li>4. 接收到一个能量筒 <img src="images/energy-can.png" style="width: 20px;height: auto"> 恢复 ' + Game.cfg.CATCH_ENERGY_CAN_GAIN + '% 生命力；</li>' +
                '<li>5. 装弹量 50，每接收到一个弹药箱 <img src="images/ammo-box.png" style="height: 20px;width: auto"> ，得 ' + Game.cfg.CATCH_AMMO_BOX_GAIN + ' 枚导弹；</li>' +
                '<li>6. 得分为 0 或者生命值用完，游戏结束。</li>';

            document.getElementById("instruction").innerHTML = instruction;

            // 动态更换背景
            var bkgno = Math.floor(1 + Math.random() * (6 - 1 + 1));
            document.getElementById("battle-field").style.backgroundImage = "url(./images/background" + bkgno + ".jpg)";
            document.getElementById("start-screen").style.backgroundImage = "url(./images/background" + bkgno + ".jpg)";
        },

        init_info() {
            document.getElementById("score").innerText = Game.cfg.INIT_SCORE;
            document.getElementById("missile-count").innerText = Game.cfg.HERO_MISSILE_COUNT_MAX;
            H.q("difficulty-text").innerText = Game.difficulty;
            H.q("life-indicator").value = Game.hero.life_value;
            H.q("life-indicator-text").innerText = Game.hero.life_value + "%";


        },


        start() {

            Game.hero.draw();
            Game.sounds.background.play();
            var fps = 60;
            var now;
            var then = Date.now();
            var interval = 1000 / fps;
            var delta;

            function loop() {

                if (!Game.is_over && !Game.paused) {
                    Game.intv = requestAnimationFrame(loop);
                    now = Date.now();
                    delta = now - then;

                    if (delta > interval) {
                        // update time stuffs

                        // Just `then = now` is not enough.
                        // Lets say we set fps at 10 which means
                        // each frame must take 100ms
                        // Now frame executes in 16ms (60fps) so
                        // the loop iterates 7 times (16*7 = 112ms) until
                        // delta > interval === true
                        // Eventually this lowers down the FPS as
                        // 112*10 = 1120ms (NOT 1000ms).
                        // So we have to get rid of that extra 12ms
                        // by subtracting delta (112) % interval (100).
                        // Hope that makes sense.

                        then = now - (delta % interval);

                        // ... Code for Drawing the Frame ...
                        Game.hero.move();
                        Game.enemies.move();
                        Game.energy_cans.move();
                        Game.ammo_boxes.move();
                        Game.hero_missiles.move();
                        Game.hero_missiles.destroyDoneRoles();
                        drawExplosion();
                    }
                } else {
                    if (Game.is_over) {
                        cancelAnimationFrame(Game.intv);
                    }
                    if (Game.paused) {
                        Game.sounds.background.pause();
                    }

                }

            }

            loop();
        },


        over() {
            Game.is_over = true;
            for (var key in Game.sounds) {
                if (Game.sounds.hasOwnProperty(key)) {
                    Game.sounds[key].pause();
                }
            }
            document.getElementById("game-over").style.display = "flex";
            var data = JSON.parse(localStorage.getItem(config.GAME_DATA_KEY)) || [];
            data.push({
                'player': Game.current_user,
                'difficulty': Game.difficulty,
                'score': Game.hero.score
            });
            data = data.filter(e => e.difficulty == Game.difficulty);
            data.sort((a, b) => a.score > b.score ? -1 : 1);
            document.getElementById("rank").innerHTML = "";
            for (var i = 0; i < data.length; i++) {
                var self_found = data[i].player == Game.current_user && data[i].score == Game.hero.score;
                var hlight = self_found ? 'hlight' : '';
                if (i < 3 || i >= 3 && self_found) {
                    document.getElementById("rank").innerHTML +=
                        '<tr class="' + hlight + '">' +
                        '	<td>' + (i + 1) + '</td>' +
                        '	<td>' + data[i].player + '</td>' +
                        '	<td>' + data[i].difficulty + '</td>' +
                        '	<td>' + data[i].score + '</td>' +
                        '</tr>';

                }
                if (self_found && i >= 3) {
                    break;
                }
            }
            localStorage.setItem(config.GAME_DATA_KEY, JSON.stringify(data));
        }
    }

    Game.init();

})();